//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
/*
 *  serial-pxausb.c : "serial-over-usb" driver for BLOB
 *
 *  Copyright (c) 2004, Intel Corporation (alek.du@intel.com)
 *  slightly modified from Ether-bvdusb.c which is from
 *  Copyright (c) 2003, Intel Corporation (yu.tang@intel.com)
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#if 0
#ifdef HAVE_CONFIG_H
# include <blob/config.h>
#endif

#include <blob/arch.h>
#include <blob/types.h>
#include <blob/time.h>
#include <blob/serial.h>
#include <blob/util.h>
#include <blob/init.h>
#include <blob/serial_usb.h>

#include <pxa_usb.h>

#else

#include "config.h"
#include "pxa_usb.h"
#include "types.h"
#include "util.h"
#include "bulverde.h"
#include "bvd_usb_ctl.h"
#include "tat.h"

#endif

static int usb_rsize=64;
static int usb_wsize=64;

/* cable connection : defined in bvd_usb_ctl.c */
extern int usb_connected;

/* receive buffer management */
static u8  rx_buf[1024*64];
static int  rx_size = 0;
static int  rx_done = 0 ;

/* transmit buffer management */
static int tx_done = 0 ;

#define SERIAL_VENDOR_ID 0x8086
#define SERIAL_PRODUCT_ID 0x07d3

//The following is cited by Bill Chen
extern volatile int iTotalReceiveLength;

void usb_serial_tx_callback(int flag, int size)
{
    tx_done = 1;
}

void usb_serial_rx_callback(int flag, int size)
{
    int left;

    if( flag != 0 ) return;

    if (rx_done) {
        /* drop */
        rx_done = 0;
        rx_size = 0;
    }

    rx_size += size;

    /* end-of-packet? */
    if (size == usb_rsize) {
        left = sizeof(rx_buf) - rx_size;
        if (!left)
            goto done;

        ep2_recv(rx_buf+rx_size, (left<usb_rsize)?left:usb_rsize, usb_serial_rx_callback);
        return;
    }

done:
    rx_done = 1;

    /* setup to receive */
    ep2_recv(rx_buf, sizeof(rx_buf), usb_serial_rx_callback);

    return;
}

void usb_driver_reset(void)
{
    desc_t * pdesc = pxa_usb_get_descriptor_ptr();
    config_desc_t *cfg;
    intf_desc_t *intf;
    ep_desc_t *ep;

    /* setup device descriptor */
    pdesc->dev.idVendor = SERIAL_VENDOR_ID;
    pdesc->dev.idProduct    = SERIAL_PRODUCT_ID;
    pdesc->dev.bNumConfigurations = 1;

    cfg = (config_desc_t*) (pdesc->cdb);

    cfg->bLength             = sizeof( config_desc_t );
    cfg->bDescriptorType     = USB_DESC_CONFIG;
    cfg->wTotalLength        = make_word_c( sizeof(config_desc_t) +
                           sizeof(intf_desc_t) * 1+
                           sizeof(ep_desc_t) * 2);
    cfg->bNumInterfaces      = 1;
    cfg->bConfigurationValue = 1;
    cfg->iConfiguration      = 0;
    cfg->bmAttributes        = USB_CONFIG_BUSPOWERED;
    cfg->MaxPower            = USB_POWER( 500 );

    intf = (intf_desc_t *) ( cfg + 1);
    intf->bLength            = sizeof( intf_desc_t );
    intf->bDescriptorType    = USB_DESC_INTERFACE;
    intf->bInterfaceNumber   = 0;
    intf->bAlternateSetting  = 0;
    intf->bNumEndpoints      = 2;
    intf->bInterfaceClass    = 0xFF;
    intf->bInterfaceSubClass = 0x02;
    intf->bInterfaceProtocol = 0x01;
    intf->iInterface         = 0;

    ep = (ep_desc_t *) (intf + 1);
    ep[0].bLength             = sizeof( ep_desc_t );
    ep[0].bDescriptorType     = USB_DESC_ENDPOINT;
    ep[0].bEndpointAddress    = USB_EP_ADDRESS( 1, USB_IN );
    ep[0].bmAttributes        = USB_EP_BULK;
    ep[0].wMaxPacketSize      = make_word( 64 );
    ep[0].bInterval           = 0;

    ep[1].bLength             = sizeof( ep_desc_t );
    ep[1].bDescriptorType     = USB_DESC_ENDPOINT;
    ep[1].bEndpointAddress    = USB_EP_ADDRESS( 2, USB_OUT );
    ep[1].bmAttributes        = USB_EP_BULK;
    ep[1].wMaxPacketSize      = make_word( 64 );
    ep[1].bInterval           = 0;

    /* reset buffer */
    rx_done = 0;
    rx_size = 0;

    /* setup to receive */
    ep2_recv(rx_buf, sizeof(rx_buf), usb_serial_rx_callback);
}

unsigned char usb_connecting_check = 0;

int usb_serial_init()
{
    usb_connected = 0;

#define GPDR1_B_USB_DEC_N (0x01 << 9)
#if 0
    /* Nam9, 2004. 9. 8 */
#if 0
    ShowUsbPinState();
#endif

    /* high GPIO40 (B_USB_FULLSPEED), pull-up USBC_P line */
    GAFR1_L &= ~0x00030000;
    GPDR1 |= 0x00000100;
    GPCR1 = 0x00000100; /* Clear and set */

    /* GPIO38 (B_OTG_PWREN) */
    GAFR1_L &= ~0x00003000;
    GPDR1 |= 0x00000040; /* set as output */
    GPCR1 = 0x00000040; /* Set B_OTG_PWREN : low */

    /* GPIO35 (B_USB_DEC_N) AF0 */
    GAFR1_L &= ~0x000000C0;
    GPDR1 &= ~0x00000008; /* set as input */

    /* GPIO41 (B_OTG_ID) AF0 */
    GAFR1_L &= ~0x000C0000;
    GPDR1 &= ~0x00000200; /* set as input */

    /* GPIO22 (B_OTG_PWRFLG) AF0 */
    GAFR0_U &= ~0x00003000;
    GPDR0 &= ~0x00400000; /* set as input */

    /* high GPIO40 (B_USB_FULLSPEED), pull-up USBC_P line */
    GAFR1_L &= ~0x00030000;
    GPDR1 |= 0x00000100;
    GPSR1 = 0x00000100;

    msleep(10); /* Delay 10 ms */

#if 0
    ShowUsbPinState();
#endif
#endif

    /* Check if the usb cable is connect to PC */

    /* GPIO35 (B_USB_DEC_N) AF0 */
        /* GAFR1_L &= ~0x000C0000; */
        /* GPDR1 &= ~0x00000200; /\* set as input *\/ */
    /* if( !(GPLR1 & GPDR1_B_USB_DEC_N) ) { */
    /*  /\* Cable isn't connected *\/ */
    /*  return 1; */
    /* } */

#if 0
    if (!usb_connecting_check)
        return 1;
#endif
    /* initialize PXA USB controller */
    pxa_usb_open("usb-serial");
    pxa_usb_start();

    return 0;
}

int usb_serial_rx(char *inbuf, int size)
{
    int i=0;
    static int left=0;
    static char* lastpos=NULL;
    if (left) {// last time left some bytes not read
        if (size > left)
            size=left;
        memcpy(inbuf,lastpos,size);
        left-=size;
        lastpos+=size;
        return size;
    }

    check_usb_connection();

    for (i = 0; i < 0x1000 && !rx_done; i++) {
        if (ICPR & 0x800) {
            udc_int_hndlr(0x11, 0);
            i=0;
        }
    }

    if (!rx_done) {//timeout exit then reset setup for receive
        ep2_recv(rx_buf, sizeof(rx_buf), usb_serial_rx_callback);
    }

    if (size > rx_size)
        size = rx_size;

    memcpy(inbuf, rx_buf, size);
    left=rx_size-size;
    lastpos=rx_buf+size;

    rx_done = 0;
    rx_size = 0;

    return size;
}

int check_usb_connection(void)
{
    int cnt;

    if (usb_connected) return 0;

    /* waiting util connected */
    if( verbose_level > 0 ) printk("***** Plug-in USB cable now ****** \n");

    /* for(cnt = 0; cnt < 3; cnt++){ */
    /*  if(!usb_connected) */
    /*  { */
    /*   if ( ICPR & 0x800 ){ */
    /*    printk("check_usb_connection-----ICPR & 0x800!\n"); */
    /*    udc_int_hndlr (0x11, 0); */
    /*   } */
    /*  } */
    /*  msleep(50); */
    /* } */

    while (!usb_connected) {
        if (ICPR & 0x800)
            udc_int_hndlr(0x11, 0);
    }
    if( verbose_level > 0 ) printk("***** Capture USB cable event ****** \n");

    cnt = 0;
    while (cnt < 0x1000) {
        if (ICPR & 0x800) {
            udc_int_hndlr(0x11, 0);
            cnt = 0;
        }
        cnt ++;
    }
    if( verbose_level > 0 ) printk("***** Return check_usb_connection() ****** \n");
    return 0;
}

int usb_serial_tx(char *outbuf, int size)
{
    int tx_size = size;

    check_usb_connection();

    if (0 == (size % usb_wsize))
        size ++;

    tx_done = 0;

    ep1_send(outbuf, size, usb_serial_tx_callback);

    while (!tx_done) {
        if (ICPR & 0x800)
            udc_int_hndlr(0x11, 0);
    }

    return tx_size;
}

#if 0
/* export usb_serial_driver driver */
usb_serial_driver_t bvd_usb_serial_driver={
    init:usb_serial_init,
    read:usb_serial_rx,
    write:usb_serial_tx
};
#endif
